#include "CPaymentManager.h"
#include "CDBConnector.h"
#include "glog/logging.h"

#include <QDateTime>
#include <QDate>
#include <QTime>
#include <QVariant>

using namespace zl;

/// T_PAYMENT 表字段定义
#define KEY_ALIAS           "ALIAS"
#define KEY_ID              "ID"
#define KEY_ISFREE          "ISFREE"
#define KEY_PREPAID         "PREPAID"
#define KEY_PREPAID_AMOUNT  "PREPAID_AMOUNT"
#define KEY_TRAIL_DURATION  "TRAIL_DURATION"
#define KEY_BILLING_UNIT    "BILLING_UNIT"
#define KEY_BILLING_PERIOD  "BILLING_PERIOD"
#define KEY_BILLING_PRICE   "BILLING_PRICE"
#define KEY_BILLING_CAPS    "BILLING_CAPS"

/// T_PAIDDETAILS 表字段定义
#define KEY_ORDER_ID        "ORDER_ID"
#define KEY_CABINET_ID      "CABINET_ID"
#define KEY_BOX_ID          "BOX_ID"
#define KEY_FEE             "FEE"
#define KEY_USESTATE        "USESTATE"
#define KEY_PAY_TIME        "PAY_TIME"
#define KEY_PAY_MODE        "PAY_MODE"
#define KEY_PAY_ORDER_ID    "PAY_ORDER_ID"
#define KEY_PAY_TRADE_NO    "PAY_TRADE_NO"
#define KEY_PAY_PERPAY_ID   "PAY_PREPAY_ID"
#define KEY_CONFIRMED       "CONFIRMED"

/// cpaymentplan
CPaymentPlan::CPaymentPlan() :
    alias_(""),
    id_(-1),
    isfree_(true),
    prepaid_(0),
    prepaid_amount_(0),
    trail_duration_(0),
    need_overdue_charge_(true),
    billing_period_(0),
    billing_price_(0),
    billing_caps_(0) {}

int32_t CPaymentPlan::calculate_cost(const zl::Order &order)
{
    // 当收费方案设置为免费时，永远不产生费用
    if (isfree_)
    {
        return 0;
    }

    // 当订单状态为准备时，返回预付费金额
    if (order.status == zl::EOrderStatus_Prepared)
    {
        return calculate_get_prepaid_amount();
    }

    // 没有设置超时，不计算费用
    if (!need_overdue_charge())
    {
        return 0;
    }

    // 根据收费设置，当还在免费使用期则不产生费用
    auto beg = QDateTime::fromString(order.begin_time.c_str(), "yyyy-MM-dd hh:mm:ss");
    if (QDateTime::currentDateTime() <= beg.addSecs(trail_duration_ * 60 * 60))
    {
        return 0;
    }

    // 根据不同的计费单位设置，计算超期产生的费用
    if (billing_unit_ == 0)
    {
        // 按小时计费
        return calculate_by_hours(order.begin_time);
    }
    else if (billing_unit_ == 1)
    {
        // 按天计费
        return calculate_by_days(order.begin_time);
    }
    else
    {
        // 错误的计费单位，不产生费用
        return 0;
    }
}

int32_t CPaymentPlan::calculate_get_prepaid_amount()
{
    // 当没有设置预付费选项时，返回0
    if (prepaid_ == 0)
    {
        return 0;
    }

    return prepaid_amount_;
}

int32_t CPaymentPlan::calculate_by_hours(const std::string& begtime)
{
    // 计算计费时长（使用总时长 - 免费时长）
    auto beg = QDateTime::fromString(begtime.c_str(), "yyyy-MM-dd hh:mm:ss");

    // 若当前订单结束时间为空，则以当前时间为结点计算
    auto end = QDateTime::currentDateTime();
    auto dur = beg.secsTo(end) - (trail_duration_ * 60 * 60);

    // 计费方案参数不正确返回0
    if (billing_period_ <= 0 || billing_price_ <= 0 || billing_caps_ <= 0)
    {
        return 0;
    }

    // 计算应收费周期数
    int cnt = dur / (billing_period_ * 60 * 60);
    int mold = dur % (billing_period_ * 60 * 60);
    if (mold > 60)
    {
        cnt += 1;
    }

    // 若已达到或超过最大收费周期数则按最大收费周期数收费
    cnt = cnt > billing_caps_ ? billing_caps_ : cnt;
    int32_t total = cnt * billing_price_;

    return total;
}

int32_t CPaymentPlan::calculate_by_days(const std::string &begtime)
{
    // 获取开始/结束时间对象
    auto beg = QDateTime::fromString(begtime.c_str(), "yyyy-MM-dd hh:mm:ss");
    auto end = QDateTime::currentDateTime();

    // 计算已使用天数
    auto days = end.date().day() - beg.date().day();
    if (days <= 0)
    {
        return 0;
    }

    // 不满一天按一天算
    int cnt = days / billing_period_;
    cnt += days % billing_period_ > 0 ? 1 : 0;

    // 若已达到或超过最大收费周期数则按最大收费周期数收费
    cnt = cnt > billing_caps_ ? billing_caps_ : cnt;
    int32_t total = cnt * billing_price_;

    return total;
}

// cpaymentmanager
CPaymentManager* CPaymentManager::instance_ = nullptr;
CPaymentManager* CPaymentManager::Instance()
{
    if (instance_ == nullptr)
    {
        instance_ = new CPaymentManager;
    }

    return instance_;
}

void CPaymentManager::Release()
{
    delete instance_;
    instance_ = nullptr;
}

int32_t CPaymentManager::Initialized()
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "initialized paymentmanager...";

    return load_data(plan_list_);
}

int32_t CPaymentManager::Uninitialized()
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "uninitialized payment manager";
    plan_list_.clear();
    return zl::EResult_Success;
}

int32_t CPaymentManager::Calculate(int32_t planid, const zl::Order &order)
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "calculate order pay detail: " << order.order_id;

    for (auto& plan : plan_list_)
    {
        if (plan.id() == planid)
        {
            return plan.calculate_cost(order);
        }
    }

    return 0;
}

std::string CPaymentManager::GetTrailDuration(int32_t planid)
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "get trail duration: " << planid;

    auto dt2 = QDateTime::currentDateTime();
    dt2.setTime(QTime(0, 0, 0));
    dt2 = dt2.addDays(1);
    auto str = dt2.toString("yyyy-MM-dd hh:mm:ss").toStdString();

    for (auto& plan : plan_list_)
    {
        if (plan.id() == planid)
        {
            if (plan.billing_unit() == 0)
            {
                // 当计费单位为小时时，默认免费时长为设置的时长
                auto dt = QDateTime::currentDateTime();
                dt = dt.addSecs(plan.trail_duration() * 60 * 60);
                return dt.toString("yyyy-MM-dd hh:mm:ss").toStdString();
            }
            else
            {
                // 当计费单位为天时，默认免费时长为当天24点
                return str;
            }
        }
    }

    // default trail duration is 24 hours
    return str;
}

int32_t CPaymentManager::CreatePaymentPlan(const zl::CPaymentPlan &plan)
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "create payment plan:" << plan.id() << "@" << plan.alias();

    // 将要新加入的付费方式添加到数据库中
    QSqlQuery query = zl::CDBConnector::Instance()->CreateQuery();
    query.prepare("INSERT INTO T_PAYMENT (ALIAS, ISFREE, PREPAID, \
        PREPAID_AMOUNT, TRAIL_DURATION, BILLING_PERIOD, BILLING_PRICE, \
        BILLING_CAPS, BILLING_UNIT) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)");
    query.bindValue(0, plan.alias().c_str());
    query.bindValue(1, plan.is_free());
    query.bindValue(2, plan.prepaid());
    query.bindValue(3, plan.prepaid_amount());
    query.bindValue(4, plan.trail_duration());
    query.bindValue(5, plan.billing_period());
    query.bindValue(6, plan.billing_price());
    query.bindValue(7, plan.billing_caps());
    query.bindValue(8, plan.billing_unit());

    auto ret = zl::CDBConnector::Instance()->ExecQuery(query);
    if (ret != zl::CDBConnector::EDBError_Success)
    {
        LOG(INFO) << "insert into database failed:" << ret;
        return zl::EResult_Failed;
    }

    LOG(INFO) << "create payment plan success";
    return zl::EResult_Success;
}

int32_t CPaymentManager::UpdatePaymentPlan(const zl::CPaymentPlan &plan)
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "update payment plan:" << plan.id() << "@" << plan.alias();

    /// 更新数据库中对应的数据
    QSqlQuery query = zl::CDBConnector::Instance()->CreateQuery();
    query.prepare("UPDATE T_PAYMENT SET ALIAS=?, ISFREE=?, PREPAID=?, \
        PREPAID_AMOUNT=?, TRAIL_DURATION=?, BILLING_PERIOD=?, BILLING_PRICE=?, \
        BILLING_CAPS=?, BILLING_UNIT=? WHERE ID=?");
    query.bindValue(0, plan.alias().c_str());
    query.bindValue(1, plan.is_free());
    query.bindValue(2, plan.prepaid());
    query.bindValue(3, plan.prepaid_amount());
    query.bindValue(4, plan.trail_duration());
    query.bindValue(5, plan.billing_period());
    query.bindValue(6, plan.billing_price());
    query.bindValue(7, plan.billing_caps());
    query.bindValue(8, plan.billing_unit());
    query.bindValue(9, plan.id());

    auto ret = zl::CDBConnector::Instance()->ExecQuery(query);
    if (ret != zl::CDBConnector::EDBError_Success)
    {
        LOG(INFO) << "update payment plan failed:" << ret;
        return zl::EResult_Failed;
    }

    LOG(INFO) << "update payment plan success";
    return zl::EResult_Success;
}

int32_t CPaymentManager::DeletePaymentPlan(int32_t id)
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "delete payment plan:" << id;

    /// 从数据库中删除对应的记录
    QSqlQuery query = zl::CDBConnector::Instance()->CreateQuery();
    query.prepare("DELETE FROM T_PAYMENT WHERE ID=?");
    query.bindValue(0, id);
    auto ret = zl::CDBConnector::Instance()->ExecQuery(query);
    if (ret != zl::CDBConnector::EDBError_Success)
    {
        LOG(INFO) << "delete payment plan failed:" << ret;
        return zl::EResult_Failed;
    }

    LOG(INFO) << "delete payment success";
    return zl::EResult_Success;
}

int32_t CPaymentManager::RetrievePaymentPlan(int32_t id, zl::CPaymentPlan &plan)
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "retrive payment plan with id:" << id;

    auto ret = load_data(plan_list_);
    if (ret != zl::EResult_Success)
    {
        LOG(ERROR) << "load payment plan list failed:" << ret;
        return zl::EResult_Failed;
    }

    for (auto& p : plan_list_)
    {
        if (p.id() == id)
        {
            plan = p;
            return zl::EResult_Success;
        }
    }
    return zl::EResult_Failed;
}

int32_t CPaymentManager::RetrievePaymentPlan(std::vector<zl::CPaymentPlan> &planlist)
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "retrive all payment plan";

    return load_data(planlist);
}

int32_t CPaymentManager::load_data(std::vector<zl::CPaymentPlan> &planlist)
{
    LOG(INFO) << "load payment plan list data from database";
    QSqlQuery query = zl::CDBConnector::Instance()->CreateQuery();
    query.prepare("SELECT * FROM T_PAYMENT");
    auto ret = zl::CDBConnector::Instance()->ExecQuery(query);
    if (ret != zl::CDBConnector::EDBError_Success)
    {
        LOG(INFO) << "load payment plan list from database failed:" << ret;
        return zl::EResult_Failed;
    }

    planlist.clear();
    while (query.next())
    {
        CPaymentPlan plan;
        plan.set_alias(query.value(KEY_ALIAS).toString().toStdString());
        plan.set_id(query.value(KEY_ID).toInt());
        plan.set_free(query.value(KEY_ISFREE).toInt());
        plan.set_prepaid(query.value(KEY_PREPAID).toInt());
        plan.set_prepaid_amount(query.value(KEY_PREPAID_AMOUNT).toInt());
        plan.set_trail_duration(query.value(KEY_TRAIL_DURATION).toInt());
        plan.set_billing_period(query.value(KEY_BILLING_PERIOD).toInt());
        plan.set_billing_price(query.value(KEY_BILLING_PRICE).toInt());
        plan.set_billing_caps(query.value(KEY_BILLING_CAPS).toInt());
        plan.set_billing_unit(query.value(KEY_BILLING_UNIT).toInt());
        planlist.push_back(plan);
    }

    LOG(INFO) << "load paymentmanager success, count:" << planlist.size();
    return zl::EResult_Success;
}

int32_t CPaymentManager::CreatePaidDetails(const zl::PaidDetail &details)
{
    LOG(INFO) << "create paid details:" << details.order_id << "@" << details.pay_order_id;

    QSqlQuery query = zl::CDBConnector::Instance()->CreateQuery();
    query.prepare("INSERT INTO T_PAID_DETAILS (ORDER_ID, CABINET_ID, \
        BOX_ID, FEE, USESTATE, PAY_TIME, PAY_MODE, PAY_ORDER_ID, \
        PAY_TRADE_NO, PAY_PREPAY_ID, CONFIRMED) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)");
    query.bindValue(0, details.order_id.c_str());
    query.bindValue(1, details.cabinet_id.c_str());
    query.bindValue(2, details.box_id);
    query.bindValue(3, details.fee_paid);
    query.bindValue(4, details.usestate.c_str());
    query.bindValue(5, details.pay_time.c_str());
    query.bindValue(6, details.pay_mode.c_str());
    query.bindValue(7, details.pay_order_id.c_str());
    query.bindValue(8, details.pay_trade_no.c_str());
    query.bindValue(9, details.pay_prepay_id.c_str());
    auto ret = zl::CDBConnector::Instance()->ExecQuery(query);
    if (ret != zl::CDBConnector::EDBError_Success)
    {
        LOG(ERROR) << "create paid details failed:" << ret;
        return zl::EResult_Failed;
    }

    LOG(INFO) << "insert paid details into db success";
    return zl::EResult_Success;
}

int32_t CPaymentManager::UpdatePaidDetails(const zl::PaidDetail &details)
{
    LOG(INFO) << "update paid details:" << details.order_id << "@" << details.pay_order_id;
    /// todo
    return zl::EResult_Success;
}

int32_t CPaymentManager::DeletePaidDetails(const std::string &paid_orderid)
{
    LOG(INFO) << "delete paid details:" << paid_orderid;

    QSqlQuery query = zl::CDBConnector::Instance()->CreateQuery();
    query.prepare("DELETE FROM T_PAID_DETAILS WHERE PAY_ORDER_ID=?");
    query.bindValue(0, paid_orderid.c_str());
    auto ret = zl::CDBConnector::Instance()->ExecQuery(query);
    if (ret != zl::CDBConnector::EDBError_Success)
    {
        LOG(ERROR) << "delete paid details failed:" << ret;
        return zl::EResult_Failed;
    }

    LOG(INFO) << "delete paid details success";
    return zl::EResult_Success;
}

int32_t CPaymentManager::RetrievePaidDetails(std::vector<zl::PaidDetail> &detailslist)
{
    LOG(INFO) << "retrieve paid details";

    QSqlQuery query = zl::CDBConnector::Instance()->CreateQuery();
    query.prepare("SELECT * FROM T_PAID_DETAILS");
    auto ret = zl::CDBConnector::Instance()->ExecQuery(query);
    if (ret != zl::CDBConnector::EDBError_Success)
    {
        LOG(ERROR) << "retrieve paid details failed:" << ret;
        return zl::EResult_Failed;
    }

    detailslist.clear();
    while (query.next())
    {
        zl::PaidDetail details;
        details.order_id = query.value(KEY_ORDER_ID).toString().toStdString();
        details.cabinet_id = query.value(KEY_CABINET_ID).toString().toStdString();
        details.box_id = query.value(KEY_BOX_ID).toInt();
        details.fee_paid = query.value(KEY_FEE).toInt();
        details.confirmed = query.value(KEY_CONFIRMED).toInt();
        details.usestate = query.value(KEY_USESTATE).toString().toStdString();
        details.pay_mode = query.value(KEY_PAY_MODE).toString().toStdString();
        details.pay_time = query.value(KEY_PAY_TIME).toDateTime().toString("yyyy-MM-dd hh:mm:ss").toStdString();
        details.pay_order_id = query.value(KEY_PAY_ORDER_ID).toString().toStdString();
        details.pay_trade_no = query.value(KEY_PAY_TRADE_NO).toString().toStdString();
        details.pay_prepay_id = query.value(KEY_PAY_PERPAY_ID).toString().toStdString();
        detailslist.push_back(details);
    }
    LOG(INFO) << "retrieve paid details success, count:" << detailslist.size();
    return zl::EResult_Success;
}


int32_t CPaymentManager::RetrievePaidDetails(
    std::vector<PaidDetail> &detailslist,
    const std::string &begin_time,
    const std::string &end_time,
    const std::string &type)
{
    LOG(INFO) << "retrieve paid details";
    QSqlQuery query = zl::CDBConnector::Instance()->CreateQuery();
    QString str = "SELECT * FROM T_PAID_DETAILS WHERE PAY_TIME>=:begin_time AND PAY_TIME<=:end_time";
    if (!type.empty())
    {
        str += " AND PAY_MODE=:type";
    }
    str += " ORDER BY PAY_TIME DESC";

    query.prepare(str);
    query.bindValue(":begin_time", begin_time.c_str());
    query.bindValue(":end_time", end_time.c_str());
    query.bindValue(":type", type.c_str());
    int32_t ret = zl::CDBConnector::Instance()->ExecQuery(query);
    if (ret != zl::CDBConnector::EDBError_Success)
    {
        LOG(ERROR) << "get all order failed: " << ret;
        return EResult_Failed;
    }

    while (query.next())
    {
        PaidDetail pd;
        pd.order_id = query.value("ORDER_ID").toString().toStdString();
        pd.cabinet_id = query.value("CABINET_ID").toString().toStdString();
        pd.box_id = query.value("BOX_ID").toInt();
        pd.fee_paid = query.value("FEE").toInt();
        pd.confirmed = query.value("CONFIRMED").toInt();
        pd.pay_time = query.value("PAY_TIME").toDateTime().toString("yyyy-MM-dd hh:mm:ss").toStdString();
        pd.pay_mode = query.value("PAY_MODE").toString().toStdString();
        pd.pay_order_id = query.value("PAY_ORDER_ID").toString().toStdString();
        pd.pay_trade_no = query.value("PAY_TRADE_NO").toString().toStdString();
        pd.pay_prepay_id = query.value("PAY_PREPAY_ID").toString().toStdString();
        detailslist.push_back(pd);
    }
    LOG(INFO) << "get all paid details success: " << detailslist.size();
    return EResult_Success;
}


int32_t CPaymentManager::ConfirmPaidDetails(const std::string &paid_orderid)
{
    LOG(INFO) << "confirm paid details:" << paid_orderid;
    QSqlQuery query = zl::CDBConnector::Instance()->CreateQuery();
    query.prepare("UPDATE T_PAID_DETAILS SET CONFIRMED=? WHERE PAY_ORDER_ID = ?");
    query.bindValue(0, 1);
    query.bindValue(1, paid_orderid.c_str());
    auto ret = zl::CDBConnector::Instance()->ExecQuery(query);
    if (ret != zl::CDBConnector::EDBError_Success)
    {
        LOG(ERROR) << "confirm paid details failed:" << ret;
        return zl::EResult_Failed;
    }

    LOG(INFO) << "confirm paid details success";
    return zl::EResult_Success;
}

int32_t CPaymentManager::RetrieveAllUnconfirmedPaidDetails(std::vector<zl::PaidDetail> &detailslist)
{
    QSqlQuery query = zl::CDBConnector::Instance()->CreateQuery();
    query.prepare("SELECT * FROM T_PAID_DETAILS WHERE CONFIRMED=?");
    query.bindValue(0, 0);
    auto ret = zl::CDBConnector::Instance()->ExecQuery(query);
    if (ret != zl::CDBConnector::EDBError_Success)
    {
        LOG(ERROR) << "retrieve failed:" << ret;
        return zl::EResult_Failed;
    }

    detailslist.clear();
    while (query.next())
    {
        zl::PaidDetail details;
        details.order_id = query.value(KEY_ORDER_ID).toString().toStdString();
        details.cabinet_id = query.value(KEY_CABINET_ID).toString().toStdString();
        details.box_id = query.value(KEY_BOX_ID).toInt();
        details.fee_paid = query.value(KEY_FEE).toInt();
        details.confirmed = query.value(KEY_CONFIRMED).toInt();
        details.usestate = query.value(KEY_USESTATE).toString().toStdString();
        details.pay_mode = query.value(KEY_PAY_MODE).toString().toStdString();
        details.pay_time = query.value(KEY_PAY_TIME).toDateTime().toString("yyyy-MM-dd hh:mm:ss").toStdString();
        details.pay_order_id = query.value(KEY_PAY_ORDER_ID).toString().toStdString();
        details.pay_trade_no = query.value(KEY_PAY_TRADE_NO).toString().toStdString();
        details.pay_prepay_id = query.value(KEY_PAY_PERPAY_ID).toString().toStdString();
        detailslist.push_back(details);
    }
    return zl::EResult_Success;
}
